#include <string.h>
#include <iostream>
#include <time.h>

#include <QDateTime>

#include "profinet/profinet_device.h"

namespace aubo
{

namespace pi
{

// extern QFile file_log;
// extern QTextStream text_stream;

void mainTimerTick(os_timer_t * os_timer, void * arg)
{
    app_data_t * app_data = (app_data_t *)arg;
    os_event_set(app_data->main_events, EVENT_TIMER);

    // 更新心跳信号。
    time_t time_now = time(NULL);
    uint32_t time_in_s = time_now;
    uint8_t * ptr_data = (uint8_t *)&time_in_s;
    app_data->interconnection_status[0] = *(ptr_data+0);
    app_data->interconnection_status[1] = *(ptr_data+1);
    app_data->interconnection_status[2] = *(ptr_data+2);
    app_data->interconnection_status[3] = *(ptr_data+3);

    app_data->shm_profinet_state_->lock();
    uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
    profinet_state[0] = app_data->interconnection_status[0];
    profinet_state[1] = app_data->interconnection_status[1];
    profinet_state[2] = app_data->interconnection_status[2];
    profinet_state[3] = app_data->interconnection_status[3];
    app_data->shm_profinet_state_->unlock();
}

void pnetMainLoop(void * arg)
{
    std::cout << "[INFO] Profinet: Enter 'pnetMainLoop'." << std::endl;
    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetMainLoop'." << "\n";
    text_stream.flush();

    uint32_t mask = EVENT_READY_FOR_DATA | EVENT_TIMER | EVENT_ALARM | EVENT_ABORT;
    uint32_t flags = 0;
    app_data_and_stack_t * app_data_and_stack = (app_data_and_stack_t *)arg;
    app_data_t * app_data = app_data_and_stack->appdata;
    pnet_t * net = app_data_and_stack->net;

    int data_output_times = 0;
    int data_output_threshold = 20;   // 20 intervals == 20ms.

    // Logger.
    // QFile file_log("./log");
    // QTextStream text_stream;
    // if (file_log.open(QIODevice::WriteOnly | QIODevice::Text))
    // {
    //     std::cout << "Open log file." << std::endl;
    //     text_stream.setDevice(&file_log);
    // }
    // else
    // {
    //     std::cout << "[ERROR] Failed to open log file." << std::endl;
    //     return;
    // }


    // 设置Profinet状态：启动，等待连接。
    app_data->shm_profinet_state_->lock();
    uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
    profinet_state[4] = 1;
    app_data->shm_profinet_state_->unlock();

    for ( ; ; )
    {
        os_event_wait(app_data->main_events, mask, &flags, OS_WAIT_FOREVER);

        if (flags & EVENT_READY_FOR_DATA)
        {
            // std::cout << "[INFO] Profinet: Event: 'EVENT_READY_FOR_DATA'." << std::endl;

            os_event_clr(app_data->main_events, EVENT_READY_FOR_DATA); /* Re-arm */

            /* Send appl ready to profinet stack. */
            int ret = pnet_application_ready(net, app_data->main_arep);
            if (ret != 0)
            {
                std:: cout << "[ERROR] Profinet: Error returned when application telling that it is ready for data. " <<
                        "Have you set IOCS or IOPS for all subslots?" << std::endl;

                text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[ERROR] Profinet: Error returned when application telling that it is ready for data. " <<
                        "Have you set IOCS or IOPS for all subslots?" << "\n";
                text_stream.flush();

                // 设置Profinet状态：异常。
                app_data->shm_profinet_state_->lock();
                uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
                profinet_state[4] = 99;
                app_data->shm_profinet_state_->unlock();
            }
            else
            {
                // 设置Profinet状态：连接上。
                app_data->shm_profinet_state_->lock();
                uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
                profinet_state[4] = 2;
                app_data->shm_profinet_state_->unlock();

                std::cout << "[INFO] Profinet: Signal of application ready for data has been sent." << std::endl;
                text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Signal of application ready for data has been sent." << "\n";
                text_stream.flush();
            }
        }
        else if (flags & EVENT_ALARM)
        {
            // std::cout << "[INFO] Profinet: Event: 'EVENT_ALARM'." << std::endl;

            os_event_clr(app_data->main_events, EVENT_ALARM); /* Re-arm */

            // 设置Profinet状态：警告。
            app_data->shm_profinet_state_->lock();
            uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
            profinet_state[4] = 3;
            app_data->shm_profinet_state_->unlock();

            pnet_pnio_status_t pnio_status = {0, 0, 0, 0};
            int ret = pnet_alarm_send_ack(net, app_data->main_arep, &pnio_status);
            if (ret != 0)
            {
                std::cout << "[ERROR] Profinet: Error when sending alarm ACK. Error: " << ret << "." << std::endl;
                text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[ERROR] Profinet: Error when sending alarm ACK. Error: " << ret << "." << "\n";
                text_stream.flush();
            }
            else
            {
                std::cout << "[INFO] Profinet: Alarm ACK sent." << std::endl;
                text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Alarm ACK sent." << "\n";
                text_stream.flush();
            }
        }
        else if (flags & EVENT_TIMER)
        {
            // std::cout << "[INFO] Profinet: Event: 'EVENT_TIMER'." << std::endl;

            os_event_clr(app_data->main_events, EVENT_TIMER); /* Re-arm */

            if (data_output_times > data_output_threshold)
            {
                data_output_times = 0;

                app_data->shm_robot_system_state_->lock();
                uint8_t * robot_system_state = (uint8_t *)app_data->shm_robot_system_state_->data();
                for (int idx = 0; idx < app_data->shm_robot_system_state_->size(); ++idx)
                    app_data->robot_system_state[idx] = robot_system_state[idx];

                app_data->shm_robot_system_state_->unlock();

                app_data->shm_robot_io_state_->lock();
                uint8_t * robot_io_state = (uint8_t *)app_data->shm_robot_io_state_->data();
                for (int idx = 0; idx < app_data->shm_robot_io_state_->size(); ++idx)
                    app_data->robot_io_state[idx] = robot_io_state[idx];

                app_data->shm_robot_io_state_->unlock();

                app_data->shm_robot_joint_state_->lock();
                uint8_t * robot_joint_state = (uint8_t *)app_data->shm_robot_joint_state_->data();
                for (int idx = 0; idx < app_data->shm_robot_joint_state_->size(); ++idx)
                    app_data->robot_joint_state[idx] = robot_joint_state[idx];

                app_data->shm_robot_joint_state_->unlock();

                app_data->shm_user_definition_input_->lock();
                uint8_t * user_definition_input = (uint8_t *)app_data->shm_user_definition_input_->data();
                for (int idx = 0; idx < app_data->shm_user_definition_input_->size(); ++idx)
                    app_data->user_definition_input[idx] = user_definition_input[idx];

                app_data->shm_user_definition_input_->unlock();

                // 设置、更新输入数据。
                (void)pnet_input_set_data_and_iops(net, APP_API, 1, PNET_SUBMOD_CUSTOM_IDENT,
                        app_data->robot_system_state, 32, PNET_IOXS_GOOD);
                (void)pnet_input_set_data_and_iops(net, APP_API, 2, PNET_SUBMOD_CUSTOM_IDENT,
                        app_data->robot_io_state, 32, PNET_IOXS_GOOD);
                (void)pnet_input_set_data_and_iops(net, APP_API, 3, PNET_SUBMOD_CUSTOM_IDENT,
                        app_data->robot_joint_state, 128, PNET_IOXS_GOOD);
                (void)pnet_input_set_data_and_iops(net, APP_API, 4, PNET_SUBMOD_CUSTOM_IDENT,
                        app_data->user_definition_input, 128, PNET_IOXS_GOOD);

                // ToDo: 读取输出数据。
            }
            else
            {
                data_output_times++;
            }

            pnet_handle_periodic(net);
        }
        else if (flags & EVENT_ABORT)
        {
            // std::cout << "[INFO] Profinet: Event: 'EVENT_ABORT'." << std::endl;

            os_event_clr(app_data->main_events, EVENT_ABORT);

            // 设置Profinet状态：警告。
            app_data->shm_profinet_state_->lock();
            uint8_t * profinet_state = (uint8_t *)app_data->shm_profinet_state_->data();
            profinet_state[4] = 4;
            app_data->shm_profinet_state_->unlock();

            app_data->main_arep = UINT32_MAX;
            app_data->alarm_allowed = true;

            std::cout << "[INFO] Profinet: Aborting the communication." << std::endl;
            text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Aborting the communication." << "\n";
            text_stream.flush();
        }
    }

    os_timer_destroy(app_data->main_timer);
    os_event_destroy(app_data->main_events);

    std::cout << "[INFO] Profinet: Ending the application." << std::endl;
    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Ending the application." << "\n";
    text_stream.flush();

    file_log.close();
}

int pnetAdjustStackConfiguration(pnet_cfg_t * stack_config)
{
    memset(stack_config, 0, sizeof(pnet_cfg_t));

    /* For clarity, some members are set to 0 even though the entire struct is cleared.
     *
     * Note that these members are set by the sample_app main:
     *    cb_arg
     *    im_0_data.order_id
     *    im_0_data.im_serial_number
     *    eth_addr.addr
     *    ip_addr
     *    ip_gateway
     *    ip_mask
     *    station_name
     */

    /* Call-backs */
    stack_config->state_cb = pnetStateInd;
    stack_config->connect_cb = pnetConnectInd;
    stack_config->release_cb = pnetReleaseInd;
    stack_config->dcontrol_cb = pnetDControlInd;
    stack_config->ccontrol_cb = pnetCControlInd;
    stack_config->read_cb = pnetReadInd;
    stack_config->write_cb = pnetWriteInd;
    stack_config->exp_module_cb = pnetExpModuleInd;
    stack_config->exp_submodule_cb = pnetExpSubmoduleInd;
    stack_config->new_data_status_cb = pnetNewDataStatusInd;
    stack_config->alarm_ind_cb = pnetAlarmInd;
    stack_config->alarm_cnf_cb = pnetAlarmCnf;
    stack_config->alarm_ack_cnf_cb = pnetAlarmAckCnf;
    stack_config->reset_cb = pnetResetInd;
    stack_config->signal_led_cb = 0;

    /* Identification & Maintenance */
    stack_config->im_0_data.vendor_id_hi = 0xfe;
    stack_config->im_0_data.vendor_id_lo = 0xed;
    stack_config->im_0_data.im_hardware_revision = 1;
    stack_config->im_0_data.sw_revision_prefix = 'V'; /* 'V', 'R', 'P', 'U', or 'T' */
    stack_config->im_0_data.im_sw_revision_functional_enhancement = 0;
    stack_config->im_0_data.im_sw_revision_bug_fix = 0;
    stack_config->im_0_data.im_sw_revision_internal_change = 0;
    stack_config->im_0_data.im_revision_counter = 0;  /* Only 0 allowed according to standard */
    stack_config->im_0_data.im_profile_id = 0x1234;
    stack_config->im_0_data.im_profile_specific_type = 0x5678;
    stack_config->im_0_data.im_version_major = 1;
    stack_config->im_0_data.im_version_minor = 1;
    stack_config->im_0_data.im_supported = 0x001e;        /* Only I&M0..I&M4 supported */
    strcpy(stack_config->im_1_data.im_tag_function, "");
    strcpy(stack_config->im_1_data.im_tag_location, "");
    strcpy(stack_config->im_2_data.im_date, "");
    strcpy(stack_config->im_3_data.im_descriptor, "");
    strcpy(stack_config->im_4_data.im_signature, "");  /* For functional safety only */

    /* Device configuration */
    stack_config->device_id.vendor_id_hi = 0xfe;
    stack_config->device_id.vendor_id_lo = 0xed;
    stack_config->device_id.device_id_hi = 0xbe;
    stack_config->device_id.device_id_lo = 0xef;
    stack_config->oem_device_id.vendor_id_hi = 0xc0;
    stack_config->oem_device_id.vendor_id_lo = 0xff;
    stack_config->oem_device_id.device_id_hi = 0xee;
    stack_config->oem_device_id.device_id_lo = 0x01;
    strcpy(stack_config->device_vendor, "aubo-robotics");
    strcpy(stack_config->manufacturer_specific_string, "Profinet plugin");

    /* LLDP settings */
    strcpy(stack_config->lldp_cfg.port_id, "port-001");
    stack_config->lldp_cfg.ttl = 20;                /* seconds */
    stack_config->lldp_cfg.rtclass_2_status = 0;
    stack_config->lldp_cfg.rtclass_3_status = 0;
    stack_config->lldp_cfg.cap_aneg = PNET_LLDP_AUTONEG_SUPPORTED |
            PNET_LLDP_AUTONEG_ENABLED;
    stack_config->lldp_cfg.cap_phy = PNET_LLDP_AUTONEG_CAP_100BaseTX_HALF_DUPLEX |
            PNET_LLDP_AUTONEG_CAP_100BaseTX_FULL_DUPLEX;
    stack_config->lldp_cfg.mau_type = PNET_MAU_COPPER_100BaseTX_FULL_DUPLEX;

    /* Network configuration */
    stack_config->send_hello = 1;
    stack_config->dhcp_enable = 0;

    std::cout << "PnetConfig" << std::endl;
    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "PnetConfig" << "\n";
    text_stream.flush();

    return 0;
}

bool pnetPlugDAP(pnet_t *net, void *arg)
{
    std::cout << "[INFO] Profinet: Enter 'pnetPlugDAP'." << std::endl;
    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetPlugDAP'." << "\n";
    text_stream.flush();

    /* Use existing callback functions to plug the (sub-)modules */
    int ret = -1;
    ret = pnetExpModuleInd(net, arg, APP_API, PNET_SLOT_DAP_IDENT, PNET_MOD_DAP_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Module " << PNET_MOD_DAP_IDENT << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Module " << PNET_MOD_DAP_IDENT << "\n";
    text_stream.flush();

    ret = pnetExpSubmoduleInd(net, arg, APP_API, PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_IDENT,
            PNET_MOD_DAP_IDENT, PNET_SUBMOD_DAP_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Subslot " << PNET_SUBMOD_DAP_IDENT << ", Module " << PNET_MOD_DAP_IDENT <<
                 ", Submodule " << PNET_SUBMOD_DAP_IDENT << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Subslot " << PNET_SUBMOD_DAP_IDENT << ", Module " << PNET_MOD_DAP_IDENT <<
                 ", Submodule " << PNET_SUBMOD_DAP_IDENT << "\n";
    text_stream.flush();

    ret = pnetExpSubmoduleInd(net, arg, APP_API, PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_IDENT,
            PNET_MOD_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Subslot " << PNET_SUBMOD_DAP_INTERFACE_1_IDENT << ", Module " << PNET_MOD_DAP_IDENT <<
                 ", Submodule " << PNET_SUBMOD_DAP_INTERFACE_1_IDENT << std::endl;

    ret = pnetExpSubmoduleInd(net, arg, APP_API, PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_PORT_0_IDENT,
            PNET_MOD_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_PORT_0_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << PNET_SLOT_DAP_IDENT <<
                 ", Subslot " << PNET_SUBMOD_DAP_INTERFACE_1_PORT_0_IDENT << ", Module " << PNET_MOD_DAP_IDENT <<
                 ", Submodule " << PNET_SUBMOD_DAP_INTERFACE_1_PORT_0_IDENT << std::endl;

    ret = pnetExpModuleInd(net, arg, APP_API, 1, PNET_MOD_R2C_SYSTEM_STATE_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << 1 <<
                 ", Module " << PNET_MOD_R2C_SYSTEM_STATE_IDENT << std::endl;

    ret = pnetExpSubmoduleInd(net, arg, APP_API, 1, 1,
            PNET_MOD_R2C_SYSTEM_STATE_IDENT, PNET_SUBMOD_CUSTOM_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << 1 <<
                 ", Subslot " << 1 << ", Module " << PNET_MOD_R2C_SYSTEM_STATE_IDENT <<
                 ", Submodule " << PNET_SUBMOD_CUSTOM_IDENT << std::endl;

    ret = pnetExpModuleInd(net, arg, APP_API, 2, PNET_MOD_R2C_IO_STATE_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << 2 <<
                 ", Module " << PNET_MOD_R2C_IO_STATE_IDENT << std::endl;

    ret = pnetExpSubmoduleInd(net, arg, APP_API, 2, 1,
            PNET_MOD_R2C_IO_STATE_IDENT, PNET_SUBMOD_CUSTOM_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << 2 <<
                 ", Subslot " << 1 << ", Module " << PNET_MOD_R2C_IO_STATE_IDENT <<
                 ", Submodule " << PNET_SUBMOD_CUSTOM_IDENT << std::endl;

    ret = pnetExpModuleInd(net, arg, APP_API, 3, PNET_MOD_R2C_JOINT_STATE_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << 3 <<
                 ", Module " << PNET_MOD_R2C_JOINT_STATE_IDENT << std::endl;

    ret = pnetExpSubmoduleInd(net, arg, APP_API, 3, 1,
            PNET_MOD_R2C_JOINT_STATE_IDENT, PNET_SUBMOD_CUSTOM_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << 3 <<
                 ", Subslot " << 1 << ", Module " << PNET_MOD_R2C_JOINT_STATE_IDENT <<
                 ", Submodule " << PNET_SUBMOD_CUSTOM_IDENT << std::endl;

    ret = pnetExpModuleInd(net, arg, APP_API, 4, PNET_MOD_USER_DEFINITION_IDENT);
    if (ret < 0)
        return false;

    std::cout << "[INFO] Profinet: pnetExpModuleInd: api " << APP_API << ", slot " << 4 <<
                 ", Module " << PNET_MOD_USER_DEFINITION_IDENT << std::endl;

    ret = pnetExpSubmoduleInd(net, arg, APP_API, 4, 1,
            PNET_MOD_USER_DEFINITION_IDENT, PNET_SUBMOD_CUSTOM_IDENT);
    if (ret < 0)
       return false;

    std::cout << "[INFO] Profinet: pnetExpSubmoduleInd: api " << APP_API << ", slot " << 4 <<
                 ", Subslot " << 1 << ", Module " << PNET_MOD_USER_DEFINITION_IDENT <<
                 ", Submodule " << PNET_SUBMOD_CUSTOM_IDENT << std::endl;

    return true;
}

int pnetConnectInd(pnet_t * net, void * arg, uint32_t arep, pnet_result_t * p_result)
{
    std::cout << "[INFO] Profinet: Enter 'pnetConnectInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetConnectInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: Connect callback. AREP: " << app_data->main_arep <<
            " Status codes: " << p_result->pnio_status.error_code << ", " <<
            p_result->pnio_status.error_decode << ", " <<
            p_result->pnio_status.error_code_1 << ", " <<
            p_result->pnio_status.error_code_2 << ". " << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Connect callback. AREP: " << app_data->main_arep <<
            " Status codes: " << p_result->pnio_status.error_code << ", " <<
            p_result->pnio_status.error_decode << ", " <<
            p_result->pnio_status.error_code_1 << ", " <<
            p_result->pnio_status.error_code_2 << ". " << "\n";
    text_stream.flush();

    // Set pnet status.
    int status = 2;
    // app_data->setStatus(status);         // Ready for exchanging data.
    // pnet_dev->sigPNetStatus(status);     // Emit a signal.

    std::cout << "[INFO] Profinet: Connection request from the controller." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Connection request from the controller." << "\n";
    text_stream.flush();

    return 0;
}

int pnetReleaseInd(pnet_t * net, void * arg, uint32_t arep, pnet_result_t * p_result)
{
    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: Release callback. AREP: " << app_data->main_arep <<
            " Status codes: " << p_result->pnio_status.error_code << ", " <<
            p_result->pnio_status.error_decode << ", " <<
            p_result->pnio_status.error_code_1 << ", " <<
            p_result->pnio_status.error_code_2 << ". " << std::endl;

    // Set pnet status.
    int status = 1;     // Waiting for a connection.
    // pnet_dev->setStatus(status);
    // pnet_dev->sigPNetStatus(status);

    std::cout << "[INFO] Profinet: Release connection." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Release connection." << "\n";
    text_stream.flush();

    return 0;
}

int pnetDControlInd(pnet_t * net, void * arg, uint32_t arep,
        pnet_control_command_t ctrl_command, pnet_result_t * p_result)
{
    std::cout << "[INFO] Profinet: Enter 'pnetDControlInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetDControlInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: DControl callback. AREP: " << app_data->main_arep <<
            " Status codes: " << p_result->pnio_status.error_code << ", " <<
            p_result->pnio_status.error_decode << ", " <<
            p_result->pnio_status.error_code_1 << ", " <<
            p_result->pnio_status.error_code_2 << ". " << std::endl;

    return 0;
}

int pnetCControlInd(pnet_t * net, void * arg, uint32_t arep, pnet_result_t * p_result)
{
    std::cout << "[INFO] Profinet: Enter 'pnetCControlInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetCControlInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: CControl callback. AREP: " << app_data->main_arep <<
            " Status codes: " << p_result->pnio_status.error_code << ", " <<
            p_result->pnio_status.error_decode << ", " <<
            p_result->pnio_status.error_code_1 << ", " <<
            p_result->pnio_status.error_code_2 << ". " << std::endl;

    return 0;
}

int pnetStateInd(pnet_t * net, void * arg, uint32_t arep, pnet_event_values_t state)
{
    std::cout << "[INFO] Profinet: Enter 'pnetStateInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetStateInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    if (state == PNET_EVENT_ABORT)
    {
        std::cout << "[INFO] Profinet: Event 'ABORT'." << std::endl;

        text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Event 'ABORT'." << "\n";
        text_stream.flush();

        // ToDo: 告诉系统Abort，然后怎么响应。

        os_event_set(app_data->main_events, EVENT_ABORT);
    }
    else if (state == PNET_EVENT_PRMEND)
    {
        std::cout << "[INFO] Profinet: Event 'PRMEND'." << std::endl;

        text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Event 'PRMEND'." << "\n";
        text_stream.flush();

        app_data->main_arep = arep;
        os_event_set(app_data->main_events, EVENT_READY_FOR_DATA);

        /* Set IOPS for DAP slot (has same numbering as the module identifiers) */
        (void)pnet_input_set_data_and_iops(net, APP_API,
                PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_IDENT, NULL, 0, PNET_IOXS_GOOD);
        (void)pnet_input_set_data_and_iops(net, APP_API,
                PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_IDENT, NULL, 0, PNET_IOXS_GOOD);
        (void)pnet_input_set_data_and_iops(net, APP_API,
                PNET_SLOT_DAP_IDENT, PNET_SUBMOD_DAP_INTERFACE_1_PORT_0_IDENT, NULL, 0, PNET_IOXS_GOOD);

        /* Set initial data and IOPS for custom input modules, and IOCS for custom output modules */
        (void)pnet_input_set_data_and_iops(net, APP_API, 1, PNET_SUBMOD_CUSTOM_IDENT,
                app_data->robot_system_state, 32, PNET_IOXS_GOOD);

        (void)pnet_input_set_data_and_iops(net, APP_API, 2, PNET_SUBMOD_CUSTOM_IDENT,
                app_data->robot_io_state, 32, PNET_IOXS_GOOD);

        (void)pnet_input_set_data_and_iops(net, APP_API, 3, PNET_SUBMOD_CUSTOM_IDENT,
                app_data->robot_joint_state, 128, PNET_IOXS_GOOD);

        (void)pnet_input_set_data_and_iops(net, APP_API, 4, PNET_SUBMOD_CUSTOM_IDENT,
                app_data->user_definition_input, 128, PNET_IOXS_GOOD);

        (void)pnet_output_set_iocs(net, APP_API, 4, PNET_SUBMOD_CUSTOM_IDENT, PNET_IOXS_GOOD);

        (void)pnet_set_provider_state(net, true);
    }

    return 0;
}

int pnetReadInd(pnet_t * net, void * arg, uint32_t arep,
        uint32_t api, uint16_t slot, uint16_t subslot, uint16_t idx, uint16_t sequence_number,
        uint8_t ** pp_read_data, uint16_t * p_read_length, pnet_result_t * p_result)
{
    std::cout << "[INFO] Profinet: Enter 'pnetReadInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetReadInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: Parameter read callback. AREP: " << arep <<
                 " API: " << api << " Slot: " << slot <<  " Subslot: " << subslot <<
                 " Index: " << (unsigned)idx << " Sequence: " << sequence_number <<
                 " Length: " << (unsigned)*p_read_length << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Parameter read callback. AREP: " << arep <<
                 " API: " << api << " Slot: " << slot <<  " Subslot: " << subslot <<
                 " Index: " << (unsigned)idx << " Sequence: " << sequence_number <<
                 " Length: " << (unsigned)*p_read_length << "\n";
    text_stream.flush();

    if ((slot == 1) && (subslot == 1))
    {
        // Robot system state.
        app_data->shm_robot_system_state_->lock();
        uint8_t * robot_system_state = (uint8_t *)app_data->shm_robot_system_state_->data();
        if ((*p_read_length) == 32)
        {
            for (int idx = 0; idx < 32; ++idx)
                (*pp_read_data)[idx] = robot_system_state[idx];
        }
        else
        {
            std::cout << "[ERROR] Profinet: Wrong data length in slot " <<
                    slot << ", subslot " << subslot << "." << std::endl;
        }

        app_data->shm_robot_system_state_->unlock();
    }
    else if ((slot == 2) && (subslot == 1))
    {
        // Robot io state.
        app_data->shm_robot_io_state_->lock();
        uint8_t * robot_io_state = (uint8_t *)app_data->shm_robot_io_state_->data();
        if ((*p_read_length) == 32)
        {
            for (int idx = 0; idx < 32; ++idx)
                (*pp_read_data)[idx] = robot_io_state[idx];
        }
        else
        {
            std::cout << "[ERROR] Profinet: Wrong data length in slot " <<
                    slot << ", subslot " << subslot << "." << std::endl;
        }

        app_data->shm_robot_io_state_->unlock();
    }
    else if ((slot == 3) && (subslot == 1))
    {
        // Robot joint state.
        app_data->shm_robot_joint_state_->lock();
        uint8_t * robot_joint_state = (uint8_t *)app_data->shm_robot_joint_state_->data();
        if ((*p_read_length) == 128)
        {
            for (int idx = 0; idx < 128; ++idx)
                (*pp_read_data)[idx] = robot_joint_state[idx];
        }
        else
        {
            std::cout << "[ERROR] Profinet: Wrong data length in slot " <<
                    slot << ", subslot " << subslot << "." << std::endl;
        }

        app_data->shm_robot_joint_state_->unlock();
    }
    else if ((slot == 4) && (subslot == 1))
    {
        // User definition input.
        app_data->shm_user_definition_input_->lock();
        uint8_t * user_definition_input = (uint8_t *)app_data->shm_user_definition_input_->data();
        if ((*p_read_length) == 128)
        {
            for (int idx = 0; idx < 128; ++idx)
                (*pp_read_data)[idx] = user_definition_input[idx];
        }
        else
        {
            std::cout << "[ERROR] Profinet: Wrong data length in slot " <<
                    slot << ", subslot " << subslot << "." << std::endl;
        }

        app_data->shm_user_definition_input_->unlock();
    }
    else
    {
        std::cout << "[ERROR] Profinet: Unrecognized slot and subslot." << std::endl;
    }

    return 0;
}

int pnetWriteInd(pnet_t * net, void * arg, uint32_t arep,
        uint32_t api, uint16_t slot, uint16_t subslot, uint16_t idx, uint16_t sequence_number,
        uint16_t write_length, uint8_t * p_write_data, pnet_result_t * p_result)
{
    std::cout << "[INFO] Profinet: Enter 'pnetWreteInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetWreteInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[INFO] Profinet: Parameter write callback. AREP: " << arep <<
                 " API: " << api << " Slot: " << slot <<  " Subslot: " << subslot <<
                 " Index: " << (unsigned)idx << " Sequence: " << sequence_number <<
                 " Length: " << write_length << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Parameter write callback. AREP: " << arep <<
                 " API: " << api << " Slot: " << slot <<  " Subslot: " << subslot <<
                 " Index: " << (unsigned)idx << " Sequence: " << sequence_number <<
                 " Length: " << write_length << "\n";
    text_stream.flush();

    if ((slot == 4) && (subslot == 1))
    {
        // User definition input.
        app_data->shm_user_definition_output_->lock();
        uint8_t * user_definition_output = (uint8_t *)app_data->shm_user_definition_output_->data();
        if (write_length == 128)
        {
            for (int idx = 0; idx < 128; ++idx)
                user_definition_output[idx] = p_write_data[idx];
        }
        else
        {
            std::cout << "[ERROR] Profinet: Wrong data length in slot " <<
                    slot << ", subslot " << subslot << "." << std::endl;
        }

        app_data->shm_user_definition_output_->unlock();
    }
    else
    {
        std::cout << "[ERROR] Profinet: Unrecognized slot and subslot." << std::endl;
    }

    return 0;
}

int pnetExpModuleInd(pnet_t * net, void * arg, uint32_t api, uint16_t slot, uint32_t module_ident)
{
    std::cout << "[INFO] Profinet: Enter 'pnetExpModuleInd'." << std::endl;

    app_data_t * app_data = (app_data_t *)arg;

    int result = pnet_pull_module(net, api, slot);
    if (result != 0)
        std::cout << "[INFO] Profinet: Slot " << slot << " is empty." << std::endl;

    int ret = pnet_plug_module(net, api, slot, module_ident);
    if (ret != 0)
        std::cout << "[ERROR] Profinet: 'pnetExpModuleInd' failed." << std::endl;

    return ret;
}

int pnetExpSubmoduleInd(pnet_t * net, void * arg, uint32_t api,
        uint16_t slot, uint16_t subslot, uint32_t module_ident, uint32_t submodule_ident)
{
    std::cout << "[INFO] Profinet: Enter 'pnetExpSubmoduleInd'." << std::endl;

    for (int idx = 0; idx < sizeof(cfg_available_submodule_types); ++idx)
    {
        if ((cfg_available_submodule_types[idx].module_ident_nbr == module_ident) &&
                (cfg_available_submodule_types[idx].submodule_ident_nbr == submodule_ident))
        {
            std::cout << "Module ident: " << module_ident << "; " << "Submodule ident: " << submodule_ident << std::endl;

            int result = pnet_pull_submodule(net, api, slot, subslot);
            if (result != 0)
                std::cout << "[INFO] Profinet: Slot " << slot << ", Subslot " << subslot << " was empty." << std::endl;

            int ret = pnet_plug_submodule(net, api, slot, subslot, module_ident, submodule_ident,
                    cfg_available_submodule_types[idx].data_dir,
                    cfg_available_submodule_types[idx].insize,
                    cfg_available_submodule_types[idx].outsize);

            std::cout << "[INFO] Profinet: Plug submodule: Slot: " << slot << ", Subslot: " << subslot <<
                         " Module: " << module_ident << ", Submodule: " << submodule_ident << ", " <<
                         "Data direction: " << cfg_available_submodule_types[idx].data_dir << ", " <<
                         "Insize: " << cfg_available_submodule_types[idx].insize << ", " <<
                         "Outsize: " << cfg_available_submodule_types[idx].outsize << "." << std::endl;

            if (ret != 0)
                std::cout << "[ERROR] Profinet: 'pnetExpSubmoduleInd' failed." << std::endl;

            return ret;
        }
    }

    // 无法找到对应的模块、子模块。
    std::cout << "[ERROR] Profinet: Cannot find the corresponding module and submodule." << std::endl;
    return -1;
}

int pnetNewDataStatusInd(pnet_t * net, void * arg, uint32_t arep,
        uint32_t crep, uint8_t changes, uint8_t data_status)
{
    std::cout << "[INFO] Profinet: Enter 'pnetNewDataStatusInd'." << std::endl;

    return 0;
}

int pnetAlarmInd(pnet_t * net, void * arg, uint32_t arep,
        uint32_t api, uint16_t slot, uint16_t subslot, uint16_t data_len, uint16_t data_usi, uint8_t * p_data)
{
    std::cout << "[INFO] Profinet: Enter 'pnetAlarmInd'." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Enter 'pnetAlarmInd'." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    std::cout << "[WARNING] Profinet: Alarm from the controller." << std::endl;
    pnet_pnio_status_t pnio_status = {0, 0, 0, 0};
    pnet_alarm_send_ack(net, arep, &pnio_status);

    return 0;
}

int pnetAlarmCnf(pnet_t * net, void * arg, uint32_t arep, pnet_pnio_status_t * p_pnio_status)
{
    std::cout << "[INFO] Profinet: Alarm confirmation callback." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Alarm confirmation callback." << "\n";
    text_stream.flush();

    return 0;
}

int pnetAlarmAckCnf(pnet_t * net, void * arg, uint32_t arep, int res)
{
    std::cout << "[INFO] Profinet: Alarm ACK confirmation (from controller) callback." << std::endl;

    text_stream << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss") << "[INFO] Profinet: Alarm ACK confirmation (from controller) callback." << "\n";
    text_stream.flush();

    app_data_t * app_data = (app_data_t *)arg;

    os_event_set(app_data->main_events, EVENT_ALARM);

    return 0;
}

int pnetResetInd(pnet_t * net, void * arg, bool should_reset_application, uint16_t reset_mode)
{
    std::cout << "[INFO] Profinet: Reset callback." << std::endl;

    return 0;
}

} // namespace pi

} // namespace aubo
